在開始接觸 Python 的時候,很多人第一步就是打開 Jupyter Notebook 開始寫,或者直接新建一個 hello.py
,接著第二個、第三個檔案不斷增加。更常見的情況是,沒有工程經驗的人,會把所有功能全部堆進同一個檔案裡。
一開始看似方便,但隨著程式碼不斷膨脹,當一個檔案動輒兩三千行時,你會發現——它變得可怕到不敢修改。因為一旦動了,其他地方可能就整個壞掉了。
因此,工程化開發的第一步,就是要建立良好的「專案目錄結構」。這樣專案才可以隨著需求成長而保持清晰,讓不同工程師接手時也能快速理解並安全地修改
今天就讓我們從最簡單的腳本,一步一步進化到「可維護的專案結構」。
my_script/
└── main.py
這是大家最熟悉的結構,直接跑 python main.py
就能得到結果。這也是我第一次撰寫Python的時候就是這樣建立一個檔案執行,然後裡面就是印出一個「Hello World」,不管任何程式語言這樣開始就對了,甚至我連Jupyter Notebook都還不會使用。
但這樣的程式只適合一次性的任務或簡單實驗,但不適合長期維護。因此這種結構只是過渡方案,最終還是需要專案化的模組設計。
my_project/
├── docs/ # 文件 (Sphinx, MkDocs)
├── scripts/ # 指令腳本
├── notebooks/ # Jupyter 實驗檔
├── ci/ # CI/CD 設定
└── .github/ # GitHub workflow
當專案慢慢變大時,你很自然會開始把檔案分到不同的資料夾,例如:把工具程式放進 scripts/
,把實驗記錄放進 notebooks/
,甚至還會加上 CI/CD 與文件資料夾。這樣做的確比單純把所有程式碼塞在一起要好,搭配 Git 版本控制,也能讓專案看起來更「專業」。
但問題很快浮現出來:
scripts/
裡面可能會出現上百個檔案,名稱相似卻沒有人知道哪一個該用。notebooks/
彼此之間缺乏組織,常常「跑得動但沒人敢改」。所以這種結構雖然看似井然有序,但仍然缺乏真正的工程化設計。接下來我們就要看,如何利用 Package 與模組化思維,建立一個清晰、可維護的專案結構。
利用Package來做區分達到專案化的結構,這就回到程式設計的核心,你會發現物件導向的概念又再度回來了拆分Package定義每一個物件是單純的物件還是提供服務類型又或者是工具類的應用等等。
my_project/
├── pyproject.toml # 專案設定檔 (Day 3 已介紹)
├── README.md # 專案說明文件
├── src/ # 放置主要程式碼
│ └── my_project/
│ ├── __init__.py
│ ├── core.py
│ ├── data.py
│ └── model.py
└── tests/ # 測試程式碼
└── test_core.py
這種結構有幾個優點:
src/my_project/
是核心邏輯,tests/
則是測試。pyproject.toml
,可以直接安裝成套件。import my_project.core
使用,不會與其他專案衝突。src/
目錄?許多人可能會問:為什麼還要多一層 src/
,不是直接放 my_project/
就好嗎?
其實這是一種 防呆設計:
my_project/
放在專案根目錄,開發時 Python 會自動找到這個資料夾,即使沒有安裝套件也能 import
。src/
之後,強迫開發者必須透過正確的安裝或設定路徑,才能引入程式碼,避免潛在錯誤。但你說我的情境這樣分類不是很好維護欸,這就延伸到在系統設計領域直到現在一直在探討的架構問題,從三層式架構演進到六邊形或者整潔架構等等,為什麼會有這樣的設計,目的都在於兩個
這些設計理念雖然源於大型系統,但背後的核心精神——降低耦合、提高內聚——在 Python 專案中同樣適用。
當你的每一個物件之間的依賴性過高,也是會導致專案的成長以及維護面的議題,這又涉及到控制反轉和依賴注入,但在這邊我們就不將系統設計的東西再延伸下去探討,接下來介紹一下常見的幾種專案架構。
當專案進入不同領域時,基礎骨架 (src/
+ tests/
) 不變,但會依照應用情境擴充不同資料夾。以下是幾個常見範例。
A. 函式庫 / SDK(Library-first)
src/my_project/
├─ core/
├─ public_api.py # 對外暴露的明確入口
└─ version.py # 單一可信來源(Single Source of Truth)
這種專案的目標是 提供穩定 API 給其他人使用(例如第三方套件)。
core/
:放核心邏輯,不要混入框架或工具。public_api.py
:明確定義對外可見的函式/類別,避免使用者直接 import 內部實作。version.py
:維護版本號,作為 單一可信來源(Single Source of Truth),可避免版本號散落各處。👉 核心精神:API 穩定性。內部可以改動,但 public_api.py
必須盡量保持相容。
B. CLI 工具
src/my_project/
└─ adapters/cli/
├─ __init__.py
├─ main.py # 入口 (console_script)
└─ commands/ # 子命令拆檔
這類專案要提供命令列介面(例如 black
、pytest
)。
adapters/cli/
:代表輸入/輸出層(Adapter 層),負責解析命令列參數。
main.py
:CLI 的入口,通常會在 pyproject.toml
中設定 project.scripts
指向它,例如:
[project.scripts]
mycli = "my_project.adapters.cli.main:app"
commands/
:把子命令拆開,避免所有功能擠在同一支檔案。
👉 核心精神:介面與邏輯分離。CLI 層只處理命令與參數,業務邏輯應該呼叫 core/
或 services/
。
C. Web API(FastAPI / Flask 等)
src/my_project/
└─ adapters/web/
├─ app.py # 建立 ASGI/WSGI app
├─ routers/ # 路由拆分
└─ deps.py # DI / 共用相依
這類專案通常是 後端服務,要處理 HTTP 請求。
adapters/web/
:對外的網路層(Web Adapter),負責路由與請求轉換。app.py
:建立 FastAPI()
或 Flask()
物件,作為應用程式入口。routers/
:不同模組的路由拆分,例如 users.py
、orders.py
。deps.py
:集中放置依賴注入(DI),例如 DB session、認證邏輯。👉 核心精神:Web 層只處理 I/O,邏輯應放在核心模組,避免整個專案變成「超大 views.py」。
D. 資料科學 / ML 專案
my_project/
├─ data/ # .gitignore:raw/, interim/, processed/
│ ├─ raw/
│ ├─ interim/
│ └─ processed/
├─ notebooks/ # 僅探索/報告,禁寫業務邏輯
└─ src/my_project/
├─ features/
├─ training/
└─ pipelines/
這類專案偏向 資料處理與實驗,需求跟一般軟體不同。
data/
:常見的三層式資料夾結構(raw → interim → processed),並且加到 .gitignore
避免把資料推上 Git。notebooks/
:僅用於探索或報告,不要放正式邏輯;正式邏輯應該寫在 src/
下。features/
:特徵工程邏輯。training/
:訓練流程,例如模型建構、優化。pipelines/
:完整的資料流(ETL → 訓練 → 評估)。👉 核心精神:分離實驗與正式流程。Notebook 用於試驗,src/
內才是可測試、可複製的邏輯。
工程化的價值就在於「讓程式長大也不會失控」。目錄結構就是第一道關鍵防線。
雖然上面舉例的專案差異很大,但他們有以下的共同點
src/
+ tests/
。專案的目錄結構,並沒有唯一的標準答案,而是會隨著情境與團隊習慣而有所不同。如果你參與過足夠多的大小專案,就會發現結構百百種,也能慢慢看出哪些設計帶來便利,哪些則讓維護變得痛苦。這些觀察與反思,正是工程成長的開始。
那麼,你目前的專案是屬於哪一種結構呢?在維護上又遇過什麼樣的挑戰?
下一篇(Day 5),我們將進一步實作 Hatch 的基本操作,看看如何用它來建立與管理虛擬環境,讓專案的可維護性再升級 🚀。